/*
 * Copyright 2007-2009 Perttu "celeron55" Ahola <celeron55@gmail.com>
 *
 * This file is part of jukumagic.
 *
 * jukumagic is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * jukumagic is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with jukumagic.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "common.h"
#include "board.h"
#include "jumperwire.h"
#include "jukuapp.h"
#include "ratsnest.h"
#include "component.h"
#include "pin.h"
#include "cut.h"
#include "narrowcut.h"
#include <algorithm>
#include <fstream>
#include <iostream>

/*
	Private methods start here
*/

void Board::ChildChanged(ObjectChangeType type, JukuObject *obj)
{
	ObjectContainer::ChildChanged(type, obj);
	ReportChange(type);

	if(type == OBJECT_CHANGE_SELECTION){
		//std::cerr<<"Board: child selection changed"<<std::endl;
		ChangedSelection();
	}
	else if(type == OBJECT_CHANGE_CONTENTS){
		//std::cerr<<"Board: child contents changed"<<std::endl;
		ChangedSomething();
	}

	if(type == OBJECT_CHANGE_CONTENTS)
	{
		std::cerr<<"Board: Should update automatic cuts"<<std::endl;
	}
}

void Board::SelfChanged(ObjectChangeType type)
{
	ObjectContainer::SelfChanged(type);

	if(type == OBJECT_CHANGE_SELECTION){
		//std::cerr<<"Board: self selection changed"<<std::endl;
		ChangedSelection();
	}
	else if(type == OBJECT_CHANGE_CONTENTS){
		//std::cerr<<"Board: self contents changed"<<std::endl;
		ChangedSomething();
	}

	if(type == OBJECT_CHANGE_CONTENTS)
	{
		std::cerr<<"Board: Should update automatic cuts"<<std::endl;
	}
}

void Board::DrawObjectsIfNeeded(wxDC &dc, wxWindow *window, wxPoint origin, JukuObjectType type, int side)
{
	for(std::list<JukuObject*>::const_iterator i = GetObjects().begin(); i != GetObjects().end(); i++){
		JukuObject *object = (*i);
		
		if(type != -1 && object->GetType() != type)
			continue;

		if(side != -1 && object->GetSide() != side)
			continue;
			
		if(window){
			BoardRect br = object->GetDrawArea();
			wxRect update_rect(
					dc.LogicalToDeviceXRel(br.m_x + origin.x),
					dc.LogicalToDeviceYRel(br.m_y + origin.y),
					dc.LogicalToDeviceXRel(br.m_w),
					dc.LogicalToDeviceYRel(br.m_h));
			if (window->IsExposed(update_rect)){
				object->Draw(dc, window, origin);
			}
			else{
			}
		}
		else{
			object->Draw(dc, NULL, origin);
		}
	}
}

void Board::GetConnectionMap(std::vector<ConnectionMapNode> &nodes)
{
	std::cout<<"Board::GetConnectionMap() (this shouldn't be called often)"<<std::endl;
	nodes.clear();
	ConnectionMapNode n;
	n.cut = false;
	n.narrowcut = false;
	for(int i=0; i<m_h_holes*m_w_holes; i++) nodes.push_back(n);
		
	for(std::list<JukuObject*>::const_iterator i = GetObjects().begin(); i != GetObjects().end(); i++){
		JukuObject *object = (*i);
		if(object->GetType() == TYPE_CUT){
			Cut *cut = (Cut*)object;
			BoardHPoint hp(cut->GetBoardPos());
			if(hp.m_y < 0 || hp.m_y >= m_h_holes || hp.m_x < 0 || hp.m_x >= m_w_holes){
				std::cout<<"overboard cut at ("<<hp.m_x<<","<<hp.m_y<<")"<<std::endl;
				continue;
			}
			nodes[hp.m_y*m_w_holes+hp.m_x].cut = true;
		}
		else if(object->GetType() == TYPE_NARROWCUT){
			NarrowCut *ncut = (NarrowCut*)object;
			BoardHPoint hp(ncut->GetBoardPos());
			if(hp.m_y < 0 || hp.m_y >= m_h_holes || hp.m_x < 0 || hp.m_x >= m_w_holes - 1){
				std::cout<<"overboard narrowcut at ("<<hp.m_x<<","<<hp.m_y<<")"<<std::endl;
				continue;
			}
			nodes[hp.m_y*m_w_holes+hp.m_x].narrowcut = true;
		}
		else if(object->GetType() == TYPE_JUMPER_WIRE){
			JumperWire *wire = (JumperWire*)object;
			BoardPoint p;
			p = wire->GetHeadPoint(1);
			int x1 = (p.m_x + 50)/100;
			int y1 = (p.m_y + 50)/100;
			if(x1 < 0 || x1 >= m_w_holes || y1 < 0 || y1 >= m_h_holes){
				std::cout<<"overboard wire head at ("<<x1<<","<<y1<<")"<<std::endl;
				continue;
			}
			p = wire->GetHeadPoint(2);
			int x2 = (p.m_x + 50)/100;
			int y2 = (p.m_y + 50)/100;
			if(x2 < 0 || x2 >= m_w_holes || y2 < 0 || y2 >= m_h_holes){
				std::cout<<"overboard wire head at ("<<x2<<","<<y2<<")"<<std::endl;
				continue;
			}
			/*BoardHPoint hp;
			hp = BoardHPoint(x2,y2);*/
			nodes[y1*m_w_holes+x1].cons.push_back(y2*m_w_holes+x2);
			//hp = BoardHPoint(x1,y1);
			nodes[y2*m_w_holes+x2].cons.push_back(y1*m_w_holes+x1);
		}
	}
}

std::vector<ConnectionMapNode>& Board::GetCacheConnectionMap()
{
	CalcConnectionMapIfNeeded();
	return m_conmap_cache;
}

void Board::CalcConnectionMapIfNeeded()
{
	if(m_conmap_needs_refreshing){
		GetConnectionMap(m_conmap_cache);
		m_conmap_needs_refreshing = false;
	}
}

bool Board::GetContinuousCopperExtent(int x0, int y, int *x1, int *x2,
		std::vector<ConnectionMapNode> &nodes, int w)
{
	if(nodes[y*w+x0].cut) return false;
	*x1 = x0;
	if(*x1 >= 1)
		for(;;){
			if(nodes[y*w+(*x1)-1].cut) break;
			if(nodes[y*w+(*x1)-1].narrowcut) break;
			(*x1)--;
			if(*x1 == 0) break;
		}
	*x2 = x0;
	if(*x2 <= w - 2)
		for(;;){
			if(nodes[y*w+(*x2)+1].cut) break;
			if(nodes[y*w+(*x2)+0].narrowcut) break;
			(*x2)++;
			if(*x2 == w - 1) break;
		}
	
	//now x1 is the leftmost x and x2 is the rightmost x of the continuous copper
	return true;
}

/*void Board::GetCopperConnected(BoardHPoint p1, std::list<BoardHPoint> &points,
		std::vector< std::vector<ConnectionMapNode> > &nodes)
{
	points.clear();
	if(p1.m_x < 0 || p1.m_x >= m_w_holes || p1.m_y < 0 || p1.m_y >= m_h_holes) return;
	int x1, x2;
	if(!GetContinuousCopperExtent(p1, &x1, &x2, nodes)){
		return;
	}
	for(int x=x1; x<=x2; x++){
		points.push_back(BoardHPoint(p1.m_y, x));
	}
}*/

/*
	Finding connections:
	- First GetCacheConnectionMap is called, which finds every point's
	  wire connections and caches the cut info.
	- When finding all connected nodes,
	  RecursiveIsConnected(p_to_find, -1, points, nodes, m_w_holes)
	  should be called.
	- When finding if something is connected,
	  RecursiveIsConnected(p1, p2, points, nodes, m_w_holes)
	  should be called.
*/
bool Board::RecursiveIsConnected(int p1, int p2, std::set<int> &points,
		std::vector<ConnectionMapNode> &nodes, int w)
{
	//std::cout<<"RecursiveIsConnected(): p1="<<p1<<", p2="<<p2<<", w="<<w<<std::endl;

	// If this is the point, we're done
	if(p1 == p2)
		return true;

	// If the point is already checked, skip it.
	// This is a fast operation because it's an std::set
	if(find(points.begin(), points.end(), p1) != points.end())
		return false;
	
	points.insert(p1);

	int fy = p1/w;
	int fx = p1-fy*w;

	//std::cout<<"p1=("<<fx<<","<<fy<<")"<<std::endl;
	//assert(fy<30);

	// Find out how long the copper around this point is
	int x1, x2;
	if(!GetContinuousCopperExtent(fx, fy, &x1, &x2, nodes, w))
		return false;
	// Check points on the copper
	for(int x=x1; x<=x2; x++){
		// If the point is connected to p2, we've found a connection
		if(RecursiveIsConnected(fy*w+x, p2, points, nodes, w))
			return true;
		// If some of the point's wires is connected to p2, we've found a connection
		for(std::list<int>::iterator i = nodes[fy*w+x].cons.begin();
				i != nodes[fy*w+x].cons.end(); i++){
			if(RecursiveIsConnected(*i, p2, points, nodes, w))
				return true;
		}
	}
	//Didn't find a connection
	return false;
}

void Board::ChangedSomething()
{
	m_conmap_needs_refreshing = true;
	for(std::list<BoardChangeHandler*>::iterator i = m_change_handlers.begin();
			i != m_change_handlers.end(); i++){
		(*i)->ContentsChanged();
	}
}

void Board::ChangedSelection()
{
	for(std::list<BoardChangeHandler*>::iterator i = m_change_handlers.begin();
			i != m_change_handlers.end(); i++){
		(*i)->SelectionChanged();
	}
}

/*
	Public methods start here
*/

void Board::InitValues()
{
	m_w_holes = 0;
	m_h_holes = 0;
	m_savescale = wxGetApp().m_settings.m_default_scale;
	m_ratsnest_visible = true;
	m_ratsnest = new RatsNest();
	m_conmap_needs_refreshing = true;
}

Board::Board()
{
	//std::cout<<"Board"<<std::endl;
	InitValues();
}

Board::Board(unsigned int w_holes, unsigned int h_holes)
{
	//std::cout<<"Board"<<std::endl;
	InitValues();
	SetSize(w_holes, h_holes);
}

Board::~Board()
{
	if(m_ratsnest) delete m_ratsnest;
	m_ratsnest = NULL;
	//std::cout<<"~Board"<<std::endl;
}

void Board::WriteXML(TiXmlNode* root)
{
    TiXmlElement *element = new TiXmlElement("Board");
    root->LinkEndChild(element);
    element->SetAttribute("w", m_w_holes);
    element->SetAttribute("h", m_h_holes);
	element->SetDoubleAttribute("scale", m_savescale);
	WriteBaseXML(element);
}

JukuObject* Board::ReadFromXMLElement(TiXmlElement* element)
{
	//std::cout<<"Board::ReadFromXMLElement()"<<std::endl;
    Board *board = new Board();

    element->Attribute("w", &(board->m_w_holes));
    element->Attribute("h", &(board->m_h_holes));
	if(!element->Attribute("scale", &board->m_savescale))
			board->m_savescale = wxGetApp().m_settings.m_default_scale;

	board->ReadBaseXML(element);

    return board;
}

void Board::SetSize(unsigned int w_holes, unsigned int h_holes)
{
	m_w_holes = w_holes;
	m_h_holes = h_holes;
	ChangedSomething();
}

void Board::Draw(wxDC &dc, wxWindow *window, wxPoint origin)
{
	/*wxPen pen(wxColour(150,150,150));
	wxBrush brush(wxColour(255,255,255));
	wxBrush brush2(wxColour(220,220,220));
	dc.SetPen(pen);*/

	/*wxPen bgpen(wxGetApp().m_settings.m_colour_board_bg, 0);
	wxBrush bgbrush(wxGetApp().m_settings.m_colour_board_bg);*/

	wxPen pen(wxGetApp().m_settings.m_colour_board_pen);
	wxBrush brush(wxGetApp().m_settings.m_colour_board_track);
	wxBrush brush2(wxGetApp().m_settings.m_colour_board_hole);

	wxPen hlpen(wxGetApp().m_settings.m_colour_board_hl, 0);
	wxBrush hlbrush(wxGetApp().m_settings.m_colour_board_hl);

	int center_y;
	
	dc.SetPen(pen);
	dc.SetBrush(brush);
	
	center_y = origin.y + 50;
	for(int y=0; y<m_h_holes; y++){
		wxRect wr(origin.x + 0, origin.y + y*100 + 10, m_w_holes*100, 80);
		wxRect wre(dc.LogicalToDeviceXRel(wr.x), dc.LogicalToDeviceYRel(wr.y),
				dc.LogicalToDeviceXRel(wr.width), dc.LogicalToDeviceYRel(wr.height));
		if(window->IsExposed(wre)){
			dc.DrawRectangle(wr);
		}
		center_y += 100;
	}

	dc.SetPen(hlpen);
	dc.SetBrush(hlbrush);
	
	for(std::set<int>::iterator i = m_hilighted_points.begin();
			i != m_hilighted_points.end(); i++){
		BoardHPoint p = IntPointToBoardHPoint(*i);
		wxRect wr(origin.x + p.m_x*100 + 10, origin.y + p.m_y*100 + 10, 80, 80);
		wxRect wre(dc.LogicalToDeviceXRel(wr.x), dc.LogicalToDeviceYRel(wr.y),
				dc.LogicalToDeviceXRel(wr.width), dc.LogicalToDeviceYRel(wr.height));
		if(window->IsExposed(wre)){
			dc.DrawRectangle(wr);
		}
	}

	dc.SetPen(pen);
	dc.SetBrush(brush2);
	
	center_y = origin.y + 50;
	for(int y=0; y<m_h_holes; y++){
		int center_x = origin.x + 50;
		for(int x=0; x<m_w_holes; x++){
			if(window->IsExposed(wxRect(dc.LogicalToDeviceXRel(center_x - wxGetApp().m_settings.m_board_hole_radius), dc.LogicalToDeviceYRel(center_y - wxGetApp().m_settings.m_board_hole_radius), dc.LogicalToDeviceXRel(wxGetApp().m_settings.m_board_hole_radius*2), dc.LogicalToDeviceYRel(wxGetApp().m_settings.m_board_hole_radius*2)))){
				dc.DrawCircle(center_x, center_y, wxGetApp().m_settings.m_board_hole_radius);
			}
			center_x += 100;
		}
		center_y += 100;
	}
	
	//set origin to first hole
	origin.x += 50;
	origin.y += 50;

	DrawObjectsIfNeeded(dc, window, origin, TYPE_JUMPER_WIRE, BOTTOM);
	DrawObjectsIfNeeded(dc, window, origin, TYPE_JUMPER_WIRE, TOP);
	DrawObjectsIfNeeded(dc, window, origin, TYPE_COMPONENT, TOP);
	DrawObjectsIfNeeded(dc, window, origin, TYPE_CUT, 0);
	DrawObjectsIfNeeded(dc, window, origin, TYPE_NARROWCUT, 0);

	if(m_ratsnest_visible && m_ratsnest) m_ratsnest->Draw(dc, window, origin);
}

JukuObject* Board::GetSelectedObject(int click_x, int click_y)
{
	for(std::list<JukuObject*>::const_iterator i = GetObjects().begin(); i != GetObjects().end(); i++){
		JukuObject *object = (*i);

		if(object->GetType() != TYPE_COMPONENT
				&& object->GetType() != TYPE_JUMPER_WIRE
				&& object->GetType() != TYPE_CUT
				&& object->GetType() != TYPE_NARROWCUT)
			continue;
		
		if(object->IsSelectedInPoint(click_x-50, click_y-50)){
			return object;
		}
	}
	return NULL;
}

std::set<JukuObject*> Board::GetSelectedObjects(int x1, int y1, int x2, int y2,
		bool touch_is_enough)
{
	std::set<JukuObject*> objects;
	for(std::list<JukuObject*>::const_iterator i = GetObjects().begin(); i != GetObjects().end(); i++){
		JukuObject *object = (*i);

		if(object->GetType() != TYPE_COMPONENT
				&& object->GetType() != TYPE_JUMPER_WIRE
				&& object->GetType() != TYPE_CUT
				&& object->GetType() != TYPE_NARROWCUT)
			continue;
		
		if(object->IsSelectedInArea(x1-50, y1-50, x2-50, y2-50, touch_is_enough)){
			objects.insert(object);
		}
	}
	return objects;
}

Pin* Board::GetPinByNetPin(NetPin &p)
{
	//std::cout<<"Board::FindNetPinPlace()"<<std::endl;
	//std::cout<<"refdes="<<p.m_refdes.fn_str()<<", num="<<p.m_num<<std::endl;
	for(std::list<JukuObject*>::const_iterator i = GetObjects().begin(); i != GetObjects().end(); i++){
		JukuObject *object = (*i);
		//std::cout<<"object->GetType()="<<object->GetType()<<std::endl;
		if(object->GetType() != TYPE_COMPONENT) continue;
		Component *c = (Component*)object;
		if(c->GetRefdes() == p.m_refdes){
			//std::cout<<"found component"<<std::endl;
			Footprint *f = c->GetFootprint();
			for(std::list<JukuObject*>::const_iterator i = f->m_pins.GetObjects().begin();
					i != f->m_pins.GetObjects().end(); i++){
				JukuObject *object = (*i);
				if(object->GetType() == TYPE_PIN){
					Pin *pin = (Pin*)object;
					if(pin->m_num == p.m_num){
						return pin;
					}
				}
			}
		}
	}
	return NULL;
}

BoardPoint Board::FindNetPinPlace(NetPin &p)
{
	Pin *pin = GetPinByNetPin(p);
	if(!pin) return BoardPoint();
	assert(pin->GetComponentPinPosition().exists);
	return pin->GetComponentPinPosition();
}

bool Board::GetNetPinByPoint(BoardHPoint p1, NetPin *npin)
{
	for(std::list<JukuObject*>::const_iterator i = GetObjects().begin(); i != GetObjects().end(); i++){
		JukuObject *object = (*i);
		if(object->GetType() != TYPE_COMPONENT) continue;
		Component *c = (Component*)object;
		Footprint *f = c->GetFootprint();
		for(std::list<JukuObject*>::const_iterator i = f->m_pins.GetObjects().begin();
				i != f->m_pins.GetObjects().end(); i++){
			JukuObject *object = (*i);
			if(object->GetType() == TYPE_PIN){
				Pin *pin = (Pin*)object;
				int x = c->GetBoardPos().m_x + pin->m_x;
				int y = c->GetBoardPos().m_y + pin->m_y;
				BoardHPoint pp(BoardPoint(x, y));
				if(pp == p1){
					npin->m_refdes = c->GetRefdes();
					npin->m_num = pin->m_num;
					return true;
				}
			}
		}
	}
	return false;
}

bool Board::GetConnected(BoardHPoint p1, std::set<int> &points)
{
	if(p1.m_x < 0 || p1.m_x >= m_w_holes || p1.m_y < 0 || p1.m_y >= m_h_holes) return false;

	std::vector<ConnectionMapNode> nodes = GetCacheConnectionMap();
	
	int p1i = p1.m_y*m_w_holes + p1.m_x;
	//it doesn't find -1 anywhere, so it searches through everything and puts
	//that everything in the points set, and that's what we want.
	RecursiveIsConnected(p1i, -1, points, nodes, m_w_holes);
	//std::cout<<"Board::GetConnected(): "<<points.size()<<" points connected"<<std::endl;
	return true;
}

bool Board::IsConnected(BoardHPoint p1, BoardHPoint p2)
{
	if(p1.m_x < 0 || p1.m_x >= m_w_holes || p1.m_y < 0 || p1.m_y >= m_h_holes) return false;
	if(p2.m_x < 0 || p2.m_x >= m_w_holes || p2.m_y < 0 || p2.m_y >= m_h_holes) return false;

	std::vector<ConnectionMapNode> nodes = GetCacheConnectionMap();
	
	std::set<int> points;
	int p1i = p1.m_y*m_w_holes + p1.m_x;
	int p2i = p2.m_y*m_w_holes + p2.m_x;
	bool is = RecursiveIsConnected(p1i, p2i, points, nodes, m_w_holes);
	//std::cout<<"Board::IsConnected(): went through "<<points.size()<<" points."<<std::endl;
	return is;
}

BoardHPoint Board::IntPointToBoardHPoint(int ip)
{
	int y = ip / GetW();
	int x = ip - y * GetW();
	return BoardHPoint(x, y);
}

void Board::AddChangeHandler(BoardChangeHandler *h)
{
	wxASSERT(h);
	// If it is not already added, add it.
	if(find(m_change_handlers.begin(), m_change_handlers.end(), h) == m_change_handlers.end()){
		m_change_handlers.push_back(h);
	}
}

void Board::RemoveChangeHandler(BoardChangeHandler *h)
{
	wxASSERT(h);
	std::list<BoardChangeHandler*>::iterator i =
			find(m_change_handlers.begin(), m_change_handlers.end(), h);
	if(i != m_change_handlers.end()){
		m_change_handlers.erase(i);
	}
}

void Board::GetComponents(std::vector<Component*> &components)
{
	for(std::list<JukuObject*>::const_iterator i = GetObjects().begin(); i != GetObjects().end(); i++){
		if((*i)->GetType() != TYPE_COMPONENT) continue;
		components.push_back((Component*)(*i));
	}
}

bool CutCompareVerticalScan(Cut *c1, Cut *c2)
{
	if(c1->GetBoardPos().m_x < c2->GetBoardPos().m_x) return true;
	if(c1->GetBoardPos().m_x > c2->GetBoardPos().m_x) return false;
	return (c1->GetBoardPos().m_y < c2->GetBoardPos().m_y);
}

Component* Board::FindComponent(wxString refdes)
{
	for(std::list<JukuObject*>::const_iterator i = GetObjects().begin();
			i != GetObjects().end(); i++){
		if((*i)->GetType() != TYPE_COMPONENT) continue;
		Component *c = (Component*)(*i);
		if(c->GetRefdes() == refdes){
			return c;
		}
	}
	return NULL;
}

void Board::ThrowComponent(Component *component)
{
	std::cerr<<"Board::ThrowComponent()"<<std::endl;
	//component->SetBoardPos(wxGetApp().m_frame->GetVisibleCenterPos());
	component->SetBoardPos(BoardPoint(1000, 1000));
	Add(component, NULL);
	component->SetSelected(true);
}

void Board::CollectOverBoardComponents()
{
	for(std::list<JukuObject*>::const_iterator i = GetObjects().begin();
			i != GetObjects().end(); i++){
		BoardRect r = (*i)->GetPinExtents();
		//std::cout<<"pin extents: x="<<r.m_x<<" y="<<r.m_y<<" w="<<r.m_w<<" h="<<r.m_h<<std::endl;
		int overx=0, overy=0;
		if(r.m_x < 0) overx = r.m_x;
		else if(r.m_x + (int)r.m_w > (GetW()-1) * 100 )
				overx = r.m_x + r.m_w - (GetW()-1) * 100;
		if(r.m_y < 0) overy = r.m_y;
		else if(r.m_y + (int)r.m_h > (GetH()-1) * 100 )
				overy = r.m_y + r.m_h - (GetH()-1) * 100;
		if(overx != 0 || overy != 0){
			(*i)->Move(-overx, -overy);
		}
	}
}
